/**
 * gnome-gmail-notifier: the gnome gmail notifier.
 * Copyright (C) 2007 Bradley A. Worley.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * 
 *     Free Software Foundation, Inc.
 *     59 Temple Place, Suite 330
 *     Boston, MA 02111-1307 USA
 **/

/*
 * include our application header.
 */
#include <main.h>

/*
 * type definitions.
 */
typedef struct _GgnAtomFeedMsg GgnAtomFeedMsg;
typedef struct _GgnAtomFeedInbox GgnAtomFeedInbox;

/*
 * gmail message object.
 */
struct _GgnAtomFeedMsg {
    /* sender information. */
    gchar* sender_name;
    gchar* sender_email;
    
    /* message information. */
    gchar* summary;
    gchar* title;
    
    /* timestamps and hyperlink. */
    gchar* modified;
    gchar* issued;
    gchar* url;
    
    /* inbox-unique identifier. */
    gchar* id;
};

/*
 * gmail inbox object.
 */
struct _GgnAtomFeedInbox {
    /* new inbox info. */
    guint count;
    gchar* url;
    
    /* timestamp. */
    gchar* modified;
    
    /* the messages. */
    GgnAtomFeedMsg* cur;
    GPtrArray* msgs;
};

/*
 * private object definition.
 */
struct _GgnAtomFeedPrivate {
    /* the libsoup session and proxy. */
    SoupSession* session;
    SoupMessage* message;
    SoupMessageBody* msgbody;
    SoupURI* proxy;
    
    /* the username and password. */
    gchar* name;
    gchar* user;
    gchar* pass;
    
    /* the proxy uri and creds. */
    gchar* prox_user;
    gchar* prox_pass;
    gchar* prox;

    /* attempts at authentication. */
    gint attempts;
    
    /* the xml parser object. */
    GgnXmlParser* parser;
    
    /* the inbox struct. */
    GgnAtomFeedInbox box;
    
    /* enabled. */
    gboolean enab;
    gboolean chkd;
    gboolean err;
};

/*
 * forward function definitions.
 */
static void ggn_atom_feed_init (GgnAtomFeed* self);
static void ggn_atom_feed_class_init (GgnAtomFeedClass* klass);
static void ggn_atom_feed_finalize (GObject* obj);

/*
 * define the gobject type and its basic functions.
 */
G_DEFINE_TYPE (GgnAtomFeed, ggn_atom_feed, G_TYPE_OBJECT);

/*
 * define the signals used.
 */
enum {
    UPDATED,
    LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };

/*
 * ggn_atom_feed_auth:
 *
 * Callback function used during the soup connection to authenticate
 * the user using the provided username and password, assuming both
 * are defined (non-NULL). Will also attempt to negotiate any proxy
 * authentication.
 *
 * Return value: void.
 */
static void ggn_atom_feed_auth (SoupSession* session,
                                SoupMessage* message,
                                SoupAuth* auth,
                                gboolean retrying,
                                gpointer data) {
    /* the userdata is the gobject. */
    GgnAtomFeed* feed = GGN_ATOM_FEED (data);

    /* avoid infinite loops by failing on retry. */
    if (retrying == TRUE)
       return;

    /* check whether it's a proxy server asking for authentication */
    if (soup_auth_is_for_proxy (auth) == TRUE) {
        /* ensure the proxy credentials are non-NULL. */
        if (feed->priv->prox_user != NULL && feed->priv->prox_pass != NULL) {
            /* authenticate the user with the proxy. */
            soup_auth_authenticate (auth,
                                    feed->priv->prox_user,
                                    feed->priv->prox_pass);
        }
    }
    else {
        /* ensure the gmail credentials are non-NULL. */
        if (feed->priv->user != NULL && feed->priv->pass != NULL) {
            /* authenticate the user with the server. */
            soup_auth_authenticate (auth, feed->priv->user, feed->priv->pass);
        }
    } 
}

/*
 * ggn_atom_feed_restart:
 *
 * Callback function used during the soup connection to indicate to
 * the application the number of attempts we've made on a feed. There
 * is no reason, since any given authenticated message should require
 * two attempts, to allow any more than three attempts at a GET.
 *
 * Return value: void.
 */
static void ggn_atom_feed_restart (SoupMessage* message, gpointer data) {
    /* grab a reference to our atom feed object. */
    GgnAtomFeed* feed = GGN_ATOM_FEED (data);

    /* if we've had three attempts, fail. */
    if (feed->priv->attempts >= GGN_ATOM_FEED_MAX_ATTEMPTS) {
        /* abort the message. */
        soup_session_abort (feed->priv->session);
    }

    /* we're attempting again. */
    feed->priv->attempts++;
}

/*
 * ggn_atom_feed_xml_begin:
 *
 * This function handles all tag openings in the parsed XML string.
 *
 * Return value: void.
 */
void ggn_atom_feed_xml_begin (GgnXmlParser* parser,
                              gchar* xpath, gchar* name,
                              GHashTable* attributes,
                              gpointer data) {
    /* cast the atom feed. */
    GgnAtomFeed* feed = GGN_ATOM_FEED (data);
    
    /* see what the path is. */
    if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY) == 0) {
        /* make a new message object. */
        feed->priv->box.cur = g_new0 (GgnAtomFeedMsg, 1);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_LINK) == 0) {
        /* read the hyperlink target. */
        feed->priv->box.url = g_strdup (g_hash_table_lookup (attributes, "href"));
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY_LINK) == 0) {
        /* read the hyperlink target. */
        feed->priv->box.cur->url = g_strdup (g_hash_table_lookup (attributes, "href"));
    }
}

/*
 * ggn_atom_feed_xml_end:
 *
 * This function handles all tag closings in the parsed XML string.
 *
 * Return value: void.
 */
void ggn_atom_feed_xml_end (GgnXmlParser* parser,
                            gchar* xpath,
                            gchar* name,
                            gpointer data) {
    /* cast the atom feed. */
    GgnAtomFeed* feed = GGN_ATOM_FEED (data);
    
    /* see what the path is. */
    if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY) == 0) {
        /* move our current message into the array. */
        g_ptr_array_add (feed->priv->box.msgs, feed->priv->box.cur);
    }
}

/*
 * ggn_atom_feed_xml_text:
 *
 * This function handles all text content in the parsed XML string.
 *
 * Return value: void.
 */
void ggn_atom_feed_xml_text (GgnXmlParser* parser,
                             gchar* xpath,
                             gchar* name,
                             gchar* value,
                             gpointer data) {
    /* cast the atom feed. */
    GgnAtomFeed* feed = GGN_ATOM_FEED (data);
    
    /* see what the path is. */
    if (g_utf8_collate (xpath, GGN_ATOM_XPATH_COUNT) == 0) {
        /* set the inbox count. */
        feed->priv->box.count = (gint) g_ascii_strtoll (value, NULL, 10);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_MODIFIED) == 0) {
        /* read in the time string. */
        feed->priv->box.modified = g_strdup (value);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY_TITLE) == 0) {
        /* read in the message title. */
        feed->priv->box.cur->title = g_strdup (value);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY_SUMMARY) == 0) {
        /* read in the message summary. */
        feed->priv->box.cur->summary = g_strdup (value);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY_MODIFIED) == 0) {
        /* read in the message timestamp. */
        feed->priv->box.cur->modified = g_strdup (value);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY_ISSUED) == 0) {
        /* read in the message timestamp. */
        feed->priv->box.cur->issued = g_strdup (value);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_ENTRY_ID) == 0) {
        /* read in the message id code. */
        feed->priv->box.cur->id = g_strdup (value);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_AUTHOR_NAME) == 0) {
        /* read in the sender information. */
        feed->priv->box.cur->sender_name = g_strdup (value);
    }
    else if (g_utf8_collate (xpath, GGN_ATOM_XPATH_AUTHOR_EMAIL) == 0) {
        /* read in the sender information. */
        feed->priv->box.cur->sender_email = g_strdup (value);
    }
}

/*
 * ggn_atom_feed_default_updated_cb:
 *
 * This is the default "updated" callback function manager.
 *
 * Return value: void.
 */
static void ggn_atom_feed_default_updated_cb (GgnAtomFeed* feed,
                                              gboolean success) {
    /* exit the function. */
    return;
}

/*
 * ggn_atom_feed_init:
 *
 * This function is used by the gobject library to
 * generate a new instance of our object.
 */
static void ggn_atom_feed_init (GgnAtomFeed* self) {
    /* set up the private data structure. */
    self->priv = g_new0 (GgnAtomFeedPrivate, 1);
    
    /* ensure the credentials are null. */
    self->priv->name = NULL;
    self->priv->user = NULL;
    self->priv->pass = NULL;
    
    /* ensure the proxy is null. */
    self->priv->prox = NULL;
    self->priv->prox_user = NULL;
    self->priv->prox_pass = NULL;
    
    /* disable the account. */
    self->priv->enab = FALSE;
    self->priv->err = FALSE;
    
    /* setup the session object, vanilla for now. */
    self->priv->session = soup_session_sync_new ();
    
    /* link the authenticate callback into our session. */
    g_signal_connect (G_OBJECT (self->priv->session),
                      "authenticate",
                      G_CALLBACK (ggn_atom_feed_auth),
                      self);
    
    /* setup the parser object. */
    self->priv->parser = ggn_xml_parser_new ();
    
    /* link the begin_element callback into our parser. */
    g_signal_connect (G_OBJECT (self->priv->parser),
                      "begin_element",
                      G_CALLBACK (ggn_atom_feed_xml_begin),
                      self);
    
    /* link the end_element callback into our parser. */
    g_signal_connect (G_OBJECT (self->priv->parser),
                      "end_element",
                      G_CALLBACK (ggn_atom_feed_xml_end),
                      self);
    
    /* link the text callback into our parser. */
    g_signal_connect (G_OBJECT (self->priv->parser),
                      "text",
                      G_CALLBACK (ggn_atom_feed_xml_text),
                      self);
}

/*
 * ggn_atom_feed_class_init:
 *
 * This function is used by the gobject library to
 * generate a new class object of our object.
 */
static void ggn_atom_feed_class_init (GgnAtomFeedClass* klass) {
    /* setup a gobject class. */
    GObjectClass* gobj_class = G_OBJECT_CLASS (klass);
    
    /* set the locations of our destruction function. */
    gobj_class->finalize = ggn_atom_feed_finalize;
    
    /* setup the default signal handler. */
    klass->updated = ggn_atom_feed_default_updated_cb;
    
    /*
     * GgnAtomFeed::updated:
     *
     * Emitted when the Atom XML feed has been successfully
     * downloaded and parsed. (Or unsuccessfully.)
     */
    signals[UPDATED] = g_signal_new ("updated",
                                     G_OBJECT_CLASS_TYPE (gobj_class),
                                     G_SIGNAL_RUN_FIRST,
                                     G_STRUCT_OFFSET (GgnAtomFeedClass,
                                                      updated),
                                     NULL, NULL,
                                     ggn_marshal_VOID__BOOLEAN,
                                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
}

/*
 * ggn_atom_feed_finalize:
 *
 * This function is used by the gobject library to cleanly finish
 * the destruction process started by the dispose function.
 */
static void ggn_atom_feed_finalize (GObject* obj) {
    /* make a reference to ourself. */
    GgnAtomFeed* self = GGN_ATOM_FEED (obj);
    
    /* free the string values. */
    g_free (self->priv->name);
    g_free (self->priv->user);
    g_free (self->priv->pass);
    g_free (self->priv->prox);
    
    /* unreference the soup session. */
    g_object_unref (self->priv->session);
    if (self->priv->proxy != NULL) {
        /* this if statement avoids a warning. */
        soup_uri_free (self->priv->proxy);
    }
    
    /* free the parser. */
    ggn_xml_parser_free (self->priv->parser);
    
    /* destroy the private object. */
    g_free (self->priv);
    self->priv = NULL;
    
    /* chain up to the parent class. */
    G_OBJECT_CLASS (ggn_atom_feed_parent_class)->finalize (obj);
}

/*
 * ggn_atom_feed_new:
 *
 * Creates a new GgnAtomFeed with default values, which are not
 * suitable for use inside of the application manager. Each new
 * atom feed object represents a GMail account that is checked.
 *
 * Return value: the new atom feed.
 */
GgnAtomFeed* ggn_atom_feed_new (void) {
    /* make a newly created gobject. */
    GgnAtomFeed* feed = g_object_new (GGN_TYPE_ATOM_FEED, NULL);
    
    /* return the new object. */
    return feed;
}

/*
 * ggn_atom_feed_free:
 *
 * Frees the given atom feed by decreasing its reference count.
 *
 * Return value: void.
 */
void ggn_atom_feed_free (GgnAtomFeed* feed) {
    /* unreference the object. */
    while (G_IS_OBJECT (feed)) {
        /* unreference this object. */
        g_object_unref (G_OBJECT (feed));
    }
}

/*
 * ggn_atom_feed_set_name:
 *
 * Sets the name value for the given GgnAtomFeed. A GMail username is
 * essentially the email address without the @gmail.com on the end.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_name (GgnAtomFeed* feed, gchar* value) {
    /* free the old string. */
    g_free (feed->priv->name);
    
    /* make a new name string. */
    feed->priv->name = g_strdup (value);
}

/*
 * ggn_atom_feed_set_user:
 *
 * Sets the user value for the given GgnAtomFeed. The usernames
 * given to the GgnAtomFeed now contain the domain name as well,
 * which means they're essentially email addresses, not usernames.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_user (GgnAtomFeed* feed, gchar* value) {
    /* free the old string. */
    g_free (feed->priv->user);
    
    /* make a new username string. */
    feed->priv->user = g_strdup (value);
}

/*
 * ggn_atom_feed_set_pass:
 *
 * Sets the pass value for the given GgnAtomFeed. The password is
 * used to authenticate the user, which must be done before the
 * atom feed is downloaded and parsed.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_pass (GgnAtomFeed* feed, gchar* value) {
    /* see if we have to free the old string. */
    g_free (feed->priv->pass);
    
    /* make a new password string. */
    feed->priv->pass = g_strdup (value);
}

/*
 * ggn_atom_feed_set_proxy:
 *
 * Sets the prox value for the given GgnAtomFeed. Some users require
 * a proxy server to succeed in connecting to a remote host, such as
 * the GMail server.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_proxy (GgnAtomFeed* feed, gchar* value) {
    /* see if we have to free the old string. */
    g_free (feed->priv->prox);
    
    /* make a new proxy string. */
    feed->priv->prox = g_strdup (value);
    
    /* see if we need to free the old URI. */
    if (feed->priv->proxy != NULL) {
        /* free the proxy uri. */
        soup_uri_free (feed->priv->proxy);
    }
    
    /* make a new proxy uri. */
    feed->priv->proxy = soup_uri_new (feed->priv->prox);
    
    /* refresh the proxy-uri property of the session */
    if (feed->priv->proxy != NULL) {
        /* ensuring the proxy has been made, of course. */
        GValue val = {0,};
        g_value_init (&val, G_TYPE_POINTER);
        g_value_set_pointer (&val, feed->priv->proxy);
        g_object_set_property (G_OBJECT (feed->priv->session),
                               SOUP_SESSION_PROXY_URI,
                               &val);
    }
}

/*
 * ggn_atom_feed_set_proxy_user:
 *
 * Sets the user value for the proxy authentication
 *
 * Return value: void.
 */
void ggn_atom_feed_set_proxy_user (GgnAtomFeed* feed, gchar* value) {
    /* free the old string. */
    g_free (feed->priv->prox_user);
    
    /* make a new username string. */
    feed->priv->prox_user = g_strdup (value);
}

/*
 * ggn_atom_feed_set_proxy_pass:
 *
 * Sets the password string used during proxy authentication.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_proxy_pass (GgnAtomFeed* feed, gchar* value) {
    /* see if we have to free the old string. */
    g_free (feed->priv->prox_pass);
    
    /* make a new password string. */
    feed->priv->prox_pass = g_strdup (value);
}

/*
 * ggn_atom_feed_set_enabled:
 *
 * Sets whether or not the feed is in use by the application.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_enabled (GgnAtomFeed* feed, gboolean value) {
    /* set the value. */
    feed->priv->enab = value;
}

/*
 * ggn_atom_feed_set_checked:
 *
 * Sets whether or not the feed is in use by the application.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_checked (GgnAtomFeed* feed, gboolean value) {
    /* set the value. */
    feed->priv->chkd = value;
}

/*
 * ggn_atom_feed_set_error:
 *
 * Sets whether or not the feed sustained a crippling blow.
 *
 * Return value: void.
 */
void ggn_atom_feed_set_error (GgnAtomFeed* feed, gboolean value) {
    /* set the value. */
    feed->priv->err = value;
}

/*
 * ggn_atom_feed_get_name:
 *
 * Returns the Descriptive name of the Atom Feed.
 *
 * Return value: A string representing the name.
 */
gchar* ggn_atom_feed_get_name (GgnAtomFeed* feed) {
    /* see if it isn't null. */
    if (feed->priv->name != NULL) {
        /* return the string. */
        return g_strdup (feed->priv->name);
    }
    else {
        /* just return null. */
        return NULL;
    }
}

/*
 * ggn_atom_feed_get_user:
 *
 * Returns the User name used when authenticating SOUP
 * connections with the Gmail server
 *
 * Return value: A string representing the username.
 */
gchar* ggn_atom_feed_get_user (GgnAtomFeed* feed) {
    /* see if it isn't null. */
    if (feed->priv->user != NULL) {
        /* return the string. */
        return g_strdup (feed->priv->user);
    }
    else {
        /* just return null. */
        return NULL;
    }
}

/*
 * ggn_atom_feed_get_pass:
 *
 * Returns the Password used for authenticating the user during
 * SOUP connections to the Gmail server.
 *
 * Return value: A string representing the Password.
 */
gchar* ggn_atom_feed_get_pass (GgnAtomFeed* feed) {
    /* see if it isn't null. */
    if (feed->priv->pass != NULL) {
        /* return the string. */
        return g_strdup (feed->priv->pass);
    }
    else {
        /* just return null. */
        return NULL;
    }
}

/*
 * ggn_atom_feed_get_proxy:
 *
 * Returns the proxy URI string used during SOUP connections.
 *
 * Return value: A string representing the Proxy URI.
 */
gchar* ggn_atom_feed_get_proxy (GgnAtomFeed* feed) {
    /* see if it isn't null. */
    if (feed->priv->prox != NULL) {
        /* return the string. */
        return g_strdup (feed->priv->prox);
    }
    else {
        /* just return null. */
        return NULL;
    }
}

/*
 * ggn_atom_feed_get_proxy_user:
 *
 * Returns the user name used when authenticating with the proxy server.
 *
 * Return value: A string representing the username.
 */
gchar* ggn_atom_feed_get_proxy_user (GgnAtomFeed* feed) {
    /* see if it isn't null. */
    if (feed->priv->prox_user != NULL) {
        /* return the string. */
        return g_strdup (feed->priv->prox_user);
    }
    else {
        /* just return null. */
        return NULL;
    }
}

/*
 * ggn_atom_feed_get_proxy_pass:
 *
 * Returns the password used for authenticating the user with
 * the proxy server
 *
 * Return value: A string representing the Password.
 */
gchar* ggn_atom_feed_get_proxy_pass (GgnAtomFeed* feed) {
    /* see if it isn't null. */
    if (feed->priv->prox_pass != NULL) {
        /* return the string. */
        return g_strdup (feed->priv->prox_pass);
    }
    else {
        /* just return null. */
        return NULL;
    }
}

/*
 * ggn_atom_feed_get_enabled:
 *
 * Tells whether or not the feed is in use by the application.
 *
 * Return value: boolean.
 */
gboolean ggn_atom_feed_get_enabled (GgnAtomFeed* feed) {
    /* return the value. */
    return feed->priv->enab;
}

/*
 * ggn_atom_feed_get_checked:
 *
 * Tells whether or not the feed is in use by the application.
 *
 * Return value: boolean.
 */
gboolean ggn_atom_feed_get_checked (GgnAtomFeed* feed) {
    /* return the value. */
    return feed->priv->chkd;
}

/*
 * ggn_atom_feed_get_error:
 *
 * Tells whether or not the feed sustained a crippling blow.
 *
 * Return value: boolean.
 */
gboolean ggn_atom_feed_get_error (GgnAtomFeed* feed) {
    /* return the value. */
    return feed->priv->err;
}

/*
 * ggn_atom_feed_update_thread:
 *
 * This function is called as another thread, which will rejoin the
 * main body of the application after the ATOM XML has been downloaded
 * and parsed.
 *
 * Return value: void.
 */
gpointer ggn_atom_feed_update_thread (gpointer feed) {
    /* create a global return value. */
    gboolean status = TRUE;
    
    /* get the GgnAtomFeed object. */
    GgnAtomFeed* self = GGN_ATOM_FEED (feed);
    
    /* create the message object. */
    self->priv->message = soup_message_new (GGN_ATOM_FEED_GET,
                                            GGN_ATOM_FEED_URL);

    /* link the restarted callback into our message. */
    g_signal_connect (G_OBJECT (self->priv->message),
                      "restarted",
                      G_CALLBACK (ggn_atom_feed_restart),
                      self);

    /* set the number of attempts to zero, we're just starting. */
    self->priv->attempts = 0;
    
    /* send the message out and about. */
    guint retval = soup_session_send_message (self->priv->session,
                                              self->priv->message);
    
    /* check the return value. */
    if (retval == SOUP_STATUS_OK) {
        /* we got the feed. */
        self->priv->msgbody = self->priv->message->response_body;
        soup_message_body_flatten (self->priv->msgbody);
        gchar* body = g_strdup (self->priv->msgbody->data);
        
        /* see if we must free the message array. */
        if (self->priv->box.msgs != NULL) {
            /* free the pointer array. */
            g_ptr_array_free (self->priv->box.msgs, FALSE);
        }
        
        /* make a new message array. */
        self->priv->box.msgs = g_ptr_array_new ();
        
        /* begin parsing the xml. */
        if (!ggn_xml_parser_load_string (self->priv->parser, body)) {
            /* something failed. */
            status = FALSE;
        }
        
        /* free the body string. */
        g_free (body);
    }
    else {
        /* tell the user the error. */
        /* g_debug ("HTTP %d: %s. (%s: %s)", retval,
                    soup_status_get_phrase (retval),
                    _("User"),
                    ggn_atom_feed_get_username (self)); */
        
        /* set the exit status. */
        status = FALSE;
        ggn_atom_feed_set_error (self, TRUE);
    }
    
    /* close all open and unused connections. */
    soup_session_abort (self->priv->session);
    
    /* emit the "updated" signal. */
    g_signal_emit (feed, signals[UPDATED], 0, status);
    
    /* do stuff. */
    return feed;
}

/*
 * ggn_atom_feed_update:
 *
 * Creates a background thread which downloads the ATOM 0.3 Feed from
 * GMail, and then parses the inherent XML for its contents. Whenit
 * is completed, the "updated" signal is called, alerting the application
 * to the new information.
 *
 * Return value: void.
 */
void ggn_atom_feed_update (GgnAtomFeed* feed) {
    /* create the thread to run, and execute it. */
    g_thread_create (ggn_atom_feed_update_thread,
                     (gpointer) feed, FALSE, NULL);
}

/*
 * ggn_atom_feed_get_count:
 *
 * Returns the number of messages in the user's Gmail inbox.
 *
 * Return value: Inbox count.
 */
gint ggn_atom_feed_get_count (GgnAtomFeed* feed) {
    /* return the value. */
    return feed->priv->box.count;
}

/*
 * ggn_atom_feed_get_modified:
 *
 * Returns a string that represents a timestamp of the last inbox
 * modification.
 *
 * Return value: Inbox timestamp.
 */
gchar* ggn_atom_feed_get_modified (GgnAtomFeed* feed) {
    /* return the value. */
    return g_strdup (feed->priv->box.modified);
}

/*
 * ggn_atom_feed_get_url:
 *
 * Returns a string that holds a URL pointing to the address
 * of the user's Gmail inbox. Generally, this is simply the
 * following: http://mail.google.com/mail.
 *
 * Return value: Inbox URL.
 */
gchar* ggn_atom_feed_get_url (GgnAtomFeed* feed) {
    /* return the value. */
    return g_strdup (feed->priv->box.url);
}

/*
 * ggn_atom_feed_get_summary:
 *
 * Returns the text summarization of a given message in the user's
 * Gmail inbox. If the message is not found or an error occurred,
 * then NULL will be returned.
 *
 * Return value: Message summary.
 */
gchar* ggn_atom_feed_get_summary (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->summary);
}

/*
 * ggn_atom_feed_get_title:
 *
 * Returns the text title of a given message in the user's
 * Gmail inbox. If the message is not found or an error occurred,
 * then NULL will be returned.
 *
 * Return value: Message title.
 */
gchar* ggn_atom_feed_get_title (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->title);
}

/*
 * ggn_atom_feed_get_sender_name:
 *
 * Returns the sender name for a given message in the
 * user's Gmail inbox.  If the message is not found or
 * an error occurred, then NULL will be returned.
 *
 * Return value: Message sender.
 */
gchar* ggn_atom_feed_get_sender_name (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->sender_name);
}

/*
 * ggn_atom_feed_sender_email:
 *
 * Returns the sender email for a given message in the
 * user's Gmail inbox.  If the message is not found or
 * an error occurred, then NULL will be returned.
 *
 * Return value: Message sender.
 */
gchar* ggn_atom_feed_get_sender_email (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->sender_email);
}

/*
 * ggn_atom_feed_get_msgmodified:
 *
 * Returns the modification timestamp of the given message.
 * If the message is not found or an error occurred, then
 * NULL will be returned.
 *
 * Return value: Message timestamp.
 */
gchar* ggn_atom_feed_get_msgmodified (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->modified);
}

/*
 * ggn_atom_feed_get_issued:
 *
 * Returns the modification timestamp of the given message.
 * If the message is not found or an error occurred, then
 * NULL will be returned.
 *
 * Return value: Message timestamp.
 */
gchar* ggn_atom_feed_get_issued (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->issued);
}

/*
 * ggn_atom_feed_msgurl:
 *
 * Returns the URL of the given message in the user's Gmail inbox.
 * If the message is not found or an error occurred, then NULL
 * will be returned.
 *
 * Return value: Message URL.
 */
gchar* ggn_atom_feed_get_msgurl (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->url);
}

/*
 * ggn_atom_feed_get_msgid:
 *
 * Returns an identification string used by Gmail to recognize the
 * message inside the user's inbox. If the message was not found or
 * an error occurred, then NULL is returned.
 *
 * Return value: Message ID String.
 */
gchar* ggn_atom_feed_get_msgid (GgnAtomFeed* feed, gint num) {
    /* exit if we have a bounds error. */
    if ((num > feed->priv->box.count - 1) || (num < 0)) {
        /* out-of-bounds. */
        return NULL;
    }
    
    /* get a reference to our message. */
    GgnAtomFeedMsg* msg = g_ptr_array_index (feed->priv->box.msgs, num);
    
    /* return the value. */
    return g_strdup (msg->id);
}
